//the storage engine for object data
package nimblestore

import (
	"errors"
	"os"
	"strconv"
	"syscall"
)

const ChunkFileCount = 365

const (
	MaxStoreSize = (1 << 40) //1T or 2T, optionally
	MinStoreSize = (1 << 33) //8G
)

const (
	BufferedChunkFileOpt = os.O_RDWR | os.O_CREATE
	SyncChunkFileOpt     = os.O_RDWR | os.O_CREATE | os.O_SYNC
)

type Store struct {
	metaDir string
	dataDir string

	//chunk file write option
	chunkFileOpt int

	//note the chunk file is numbered as 1, 2, 3, ...
	chunkFiles map[int]*os.File
}

//start the store engine with one system disk and one data disk with the former for metadata and logging
func NewStore(metaDir string, dataDir string) (s *Store, e error) {
	s = new(Store)
	s.metaDir = metaDir
	s.dataDir = dataDir
	s.chunkFileOpt = BufferedChunkFileOpt

	//make all chunk files prepared
	e = s.coldStart()

	return s, e
}

//get free space information via statfs
func (s *Store) StatFreeSpace() int64 {
	var buf syscall.Statfs_t
	err := syscall.Statfs(s.dataDir, &buf)
	if err != nil {
		return 0
	}

	freeSpace := int64(buf.Bfree) * int64(buf.Bsize)
	if freeSpace > MaxStoreSize {
		freeSpace = MaxStoreSize
	} else if freeSpace < MinStoreSize {
		freeSpace = 0
	}

	return freeSpace
}

func (s *Store) GetWatermark(chunkId int) (offset int64, e error) {
	f, here := s.chunkFiles[chunkId]
	if !here {
		e = errors.New("FileMissed")
		return
	}
	//stat gets its current size
	var fi os.FileInfo
	fi, e = f.Stat()
	if e == nil {
		offset = fi.Size()
	}
	return
}

func (s *Store) SyncChunk(chunkId int) error {
	f, here := s.chunkFiles[chunkId]
	if !here {
		return errors.New("FileMissed")
	}

	return f.Sync()
}

func (s *Store) FullFileWrite(data []byte, chunkId int, offset int64) error {
	f, here := s.chunkFiles[chunkId]
	if !here {
		return errors.New("FileMissed")
	}

	_, e := f.WriteAt(data, offset)
	return e
}

func (s *Store) ReadDataToBuf(chunkId int, offset, size int64, buf []byte) (nbuf []byte, err error) {
	if buf == nil || int64(len(buf)) != size {
		return s.FullFileRead(chunkId, offset, size)
	}

	f, ok := s.chunkFiles[chunkId]
	if !ok {
		err = errors.New("FileMissed")
		return
	}
	nbuf = buf
	_, err = f.ReadAt(nbuf, offset)
	return
}

func (s *Store) FullFileRead(chunkId int, offset, size int64) (data []byte, err error) {
	data = make([]byte, size)
	f, ok := s.chunkFiles[chunkId]
	if ok {
		_, err = f.ReadAt(data, offset)
		return
	}

	err = errors.New("FileMissed")
	return
}

func (s *Store) Delete(chunk int, offset, size int64) error {
	return nil
}

func (s *Store) coldStart() error {
	s.chunkFiles = make(map[int]*os.File, ChunkFileCount)

	// loop over the chunk files
	var i int
	for i = 1; i <= ChunkFileCount; i++ {
		chunkFile := s.dataDir + "/" + strconv.Itoa(i)
		f, err := os.OpenFile(chunkFile, s.chunkFileOpt, 0666)
		if err != nil {
			return err
		}
		s.chunkFiles[i] = f
	}

	return nil
}
